package socket.auction.server;


import java.io.*;
import java.net.*;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.io.FileInputStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.util.encoders.Hex;

import org.apache.log4j.Logger;

import rmi.analytics.server.AnalyticsInterface;
import rmi.domain.*;
import rmi.eventing.types.UserEvent;
import rmi.management.client.ManagementClient;
import socket.domain.Messagepk;
import socket.domain.SplittLine;


//Fuer jeden Client eine eigene Session bzw. Thread
public class ClientSession extends Thread {
	public Logger log = Logger.getLogger(ClientSession.class);
	
	private Socket s;
	private InputStream in;
	private OutputStream out;
	private UserDB userdb;
	private User loginuser=null;
	private ObjectOutputStream outToClient;
	private ObjectInputStream inFromClient;
	private String bline = "";
	private AuctionManager auctman;
	private DecimalFormat f;
	private ArrayList<ClientSession> sessionDB;
	private int udpport;
	private AnalyticsInterface analyticsServ = null;
	private Key key;
	
	public ClientSession (Socket s,UserDB userdb,AuctionManager auctman,ArrayList<ClientSession>sessionDB)throws IOException{
		this.s=s;
		this.in = s.getInputStream();
		this.out = s.getOutputStream();
		this.userdb = userdb;
		this.auctman = auctman;
		this.f = new DecimalFormat("#0.00"); 
		this.sessionDB= sessionDB;
		
	}
	public void run(){
		//ACHUNT NOCH FANGEN ODER SOWIESO NACH AUSSEN LEGEN
		
		
		
			String clientSentence;
			boolean chklogin=false;
			udpport=0;
			try{
				// I/O Streams Client-Server
				inFromClient = new ObjectInputStream(in);
				outToClient = new ObjectOutputStream(out);
			}
			catch (IOException e){
				System.out.println("Error Creating In/OutputStream");
				return;
			}
		
			try {
				//Konvertiert String zu Int fuer UDPPort
				udpport = Integer.parseInt(((Messagepk)(inFromClient.readObject())).getMessage().get(0));} 
			catch (NumberFormatException e1) {e1.printStackTrace();return;} 
			catch (IOException e1) {e1.printStackTrace();return;} 
			catch (ClassNotFoundException e1) {e1.printStackTrace();return;}
			
			while (true){
				try{

					clientSentence=null;
					try {
						//Objekt vom Client
						clientSentence =((Messagepk)(inFromClient.readObject())).getMessage().get(0);
					} catch (IOException e) {
						log.info("Client disconnected from Server");
						if(!(loginuser==null)){
							loginuser.setLoggedin(false);
							sendDisconnectedEvent();
						}
						
						break;
					} catch (ClassNotFoundException e) {e.printStackTrace();break;}
					
					
					if (clientSentence!=null){
						//Splitte String auf
						SplittLine line = new SplittLine(clientSentence);
						
					//	while (line.hasNext()){
							String cmd = line.readNext();
							
							//Ueberpruefung auf !login
							if (cmd.equals("!login")){
								if(!chklogin){
									if(line.hasNext()){
										
										String username = line.readNext();
										
										if(line.hasNext()){
											
											outToClient.writeObject(new Messagepk(bline,"Unkown Parameter!"));
										}
										else{
											//Falls weitere Parameter vorhanden sind
											if(line.hasNext()){outToClient.writeObject(new Messagepk(bline,"Unkown Command!"));}
											//holt User aus der DB
											loginuser = userdb.getUser(username);
											if(!(loginuser.isLoggedin())){
												//Setze User IP
												loginuser.setIP(s.getInetAddress());
			
												//Setze User UDPPort
												//assert udpport !=0;
												loginuser.setPort(udpport);
												//Fuer Ausgabe Name des Users
												bline = loginuser.getName();
												//Sende Objekt an Client
												outToClient.writeObject(new Messagepk(bline,"Successfully logged in as "+loginuser.getName()+"!"));
												//Flag, User ist nun eingelogged
												loginuser.setLoggedin(true);
												//sendLoginEvent();
												sendLoginEvent();
												
												//Sende zwischengespeicherte Notifications
												loginuser.sendNotifications();
												chklogin = true;
												readKeyfile(loginuser.getName());
											}
											else{
												loginuser=null;
												outToClient.writeObject(new Messagepk(bline,"Login rejected! User is already connected" ));			
											}
										}
									}
									else{outToClient.writeObject(new Messagepk(bline,"Unkown Command!"));line = new SplittLine("");}
								}
								else{outToClient.writeObject(new Messagepk(bline,"Unkown Command!"));line = new SplittLine("");}
								
								
							}
							//Ueberpruefung auf !list
							else if(cmd.equals("!list")){
								if(line.hasNext()){
									outToClient.writeObject(new Messagepk(bline,"Unkown Parameter!"));line = new SplittLine("");
								}
								else{
								
									//Sendet Liste aus AuctionManager an Client
									ArrayList<Auction> auctlist = auctman.getAuctlist();
									Messagepk listmsg = new Messagepk();
									for (int i=0;i<auctlist.size();i++){
										Auction markedAuct = auctlist.get(i);
										listmsg.addMessage((markedAuct.getId()+". '"+ markedAuct.getDescription()+
												"' "+markedAuct.getCreatorName()+" " + markedAuct.getEnd()+" "+f.format(markedAuct.getHBit())+" "+markedAuct.getHBitUserName())) ;
									}
									listmsg.setUsername(bline);
									if(this.loginuser!=null){
										this.addHMAC(listmsg);
									}
									outToClient.writeObject(listmsg);
								}
							}
							
							else if(cmd.equals("!logout")){
								if(line.hasNext()){
									outToClient.writeObject(new Messagepk(bline,"Unkown Parameter!"));line = new SplittLine("");
								}
								else{
									if (!chklogin){
										//Falls nicht eingelogged
										outToClient.writeObject(new Messagepk(bline,"You have to log in first!"));
									}
									else{
										bline = "";
										outToClient.writeObject(new Messagepk(bline,"Successfully logged out as "+ loginuser.getName()));
										loginuser.setLoggedin(false);
										chklogin=false;
										//Send Logout Event
										sendLogoutEvent();
										loginuser=null;
										
									}
								}
							}
							//Ueberpruefung auf !create
							else if(cmd.equals("!create")){
								
								int duration=0;
								String description="";
								//Ob User eingelogged
								if (!chklogin){
									outToClient.writeObject(new Messagepk(bline,"Unkown Command!"));
									line = new SplittLine("");
									
								}
								else{
									
									if(line.hasNext()){
										//Parameter Pruefung
										try{
											duration= Integer.parseInt(line.readNext());
											if(line.hasNext()){
												description =line.readNext();
												while (line.hasNext()){
													description +=" "+line.readNext();
												}
												//Erstelle Neue Auktion und Message an User
												outToClient.writeObject(new Messagepk(bline,auctman.createAuction(duration, description, loginuser)));
											}
											else{
												//Falscher Parameter
												outToClient.writeObject(new Messagepk(bline,"Unkown Command!"));
												line = new SplittLine("");
											}
										}
										catch(NumberFormatException e){
											outToClient.writeObject(new Messagepk(bline,"Unkown Command!"));
											line = new SplittLine("");
										}
											
									}
									else{
										//Falscher Parameter
										outToClient.writeObject(new Messagepk(bline,"Unkown Command!"));
										line = new SplittLine("");
										
									}
								}
							}
							//Ueberpruefe auf !bid
							else if(cmd.equals("!bid")){
								int auctId=0;
								double mount=0;
								
								//Ob User eingelogged
								if (!chklogin){
									outToClient.writeObject(new Messagepk(bline,"Unkown Command!"));
									line = new SplittLine("");
									
								}
								else{
									
									if(line.hasNext()){
										try{
											
											auctId= Integer.parseInt(line.readNext());
											if(line.hasNext()){
												try{
													mount= Double.parseDouble(line.readNext());
													Auction auct = auctman.getAuct(auctId);
													if(auct==null){
														//gweuenschte Auktion ist nicht verfuegbar
														outToClient.writeObject(new Messagepk(bline,"Auction with ID "+auctId+" is not available!"));
														line = new SplittLine("");
														
													}
													else{
														if(auct.bid(mount, loginuser)){
															//Erfolgreich geboten
															outToClient.writeObject(new Messagepk(bline,"You successfully bid with " +f.format(mount)+ " on "+auct.getDescription()+"."));
														}
														else{
															//Gebot war zu niedrig
															outToClient.writeObject(new Messagepk(bline,"You unsuccessfully bid with " +f.format(mount)+ " on "+auct.getDescription()+".Current highest bid is "+f.format(auct.getHBit())+"."));
														}
													}
												}
												catch(NumberFormatException e){
													//Falscher Parameter
													outToClient.writeObject(new Messagepk(bline,"Unkown Command!"));
													line = new SplittLine("");
													
												}			
											}
											else{
												//Falscher Parameter
												outToClient.writeObject(new Messagepk(bline,"Unkown Command!"));
												line = new SplittLine("");
												
											}
										}
										catch(NumberFormatException e){
											//Falscher Parameter
											outToClient.writeObject(new Messagepk(bline,"Unkown Command!"));
											line = new SplittLine("");
										}
										
									}
									else{
										//Falscher Parameter
										outToClient.writeObject(new Messagepk(bline,"Unkown Command!"));
										line = new SplittLine("");
										
									}
								}
							}
							else{
								//Falscher Parameter
								outToClient.writeObject(new Messagepk(bline,"Unkown Command!"));
								line = new SplittLine("");
								
							}							
						}
						
					//}
				}
				catch (IOException e){
					//Client disconnected
					if(!(loginuser==null)){
					loginuser.setLoggedin(false);
					}
					sessionDB.remove(sessionDB.indexOf(this));
					log.info("Client disconnected from Server");
					break;
				}
			}

	}
	//Sendet die Shutdownnotifikation an den Client
	public void sendShutdownNotification(){
		new Notification(udpport,"!shutdown",s.getInetAddress());
	}
	//Schliesst bei shutdown die Streams
	public void closeStreams(){
		try {this.in.close();} catch (IOException e1){e1.printStackTrace();}
		try {this.out.close();} catch (IOException e) {e.printStackTrace();}
	}
	
	
	private void sendLoginEvent(){
		if(!(analyticsServ==null)){
			UserEvent ev = new UserEvent(loginuser.getName());
			ev.setType(UserEvent.Type.USER_LOGIN);
			try {
				analyticsServ.processEvent(ev);
			} catch (RemoteException e) {
				log.error("AnalyticsServer is not longer available");
				log.debug("Set Analyticsreference to null");
				analyticsServ=null;
			}
		}
	}
	private void sendLogoutEvent(){
		if(!(analyticsServ==null)){
			//Send Logout Event
			UserEvent ev = new UserEvent(loginuser.getName());
			ev.setType(UserEvent.Type.USER_LOGOUT);
			try {
				analyticsServ.processEvent(ev);
			} catch (RemoteException e1) {
				log.error("AnalyticsServer is not longer available");
				log.debug("Set Analyticsreference to null");
				analyticsServ=null;
			}
		}
			
	}
	private void sendDisconnectedEvent(){
		if(!(analyticsServ==null)){
			UserEvent ev = new UserEvent(loginuser.getName());
			ev.setType(UserEvent.Type.USER_DISCONNECTED);
			try {
				analyticsServ.processEvent(ev);
			} catch (RemoteException e1) {
				log.error("AnalyticsServer is not longer available");
				log.debug("Set Analyticsreference to null");
				analyticsServ=null;
			}
		}
	}
	public void setAnalytics( AnalyticsInterface analyticsServ){
		this.analyticsServ = analyticsServ;
	}
	
	
	//LAB 3 STAGE 2!!_______________________________________________________________________
	
	private void readKeyfile(String username){
		byte[] keyBytes = new byte[1024];
		String pathToSecretKey = "keys/"+username+".key";
		FileInputStream fis;
		try {
			fis = new FileInputStream(pathToSecretKey);
			fis.read(keyBytes);
			fis.close();
		} catch (FileNotFoundException e1) {
			System.out.println("No secretkey found for user "+username);
		}
	    catch (IOException e) {
			System.out.println("Error: Reading file");
		}
		byte[] input = Hex.decode(keyBytes);
		
		
		
		// make sure to use the right ALGORITHM for what you want to do
		// (see text)
		key = new SecretKeySpec(input,"HmacSHA256"); 
	}
	private void addHMAC(Messagepk vm){
		ArrayList<String> messagearraylist = vm.getMessage();
		String appendmessage="";
		for(int i =0; i<messagearraylist.size();i++){
			appendmessage+=messagearraylist.get(i);
		}
		byte[] generatedhmac = createHMAC(appendmessage);
		
		vm.setAuthenticationCode(generatedhmac);
	}
	private byte[] createHMAC(String plain){
		byte[]plainarray = plain.getBytes();
		Mac hMac;
		try {
			hMac = Mac.getInstance("HmacSHA256");
			hMac.init(key);
			hMac.update(plainarray);
			return hMac.doFinal();
			
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		// MESSAGE is the message to sign in bytes
		
		return null;
	}

}
